package zelix.utils.hooks.visual.font.render;


import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.util.Locale;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.util.math.MathHelper;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Vector2f;

public class TTFFontRenderer {
   private final boolean antiAlias;
   private Font font;
   private boolean fractionalMetrics;
   private CharacterData[] regularData;
   private CharacterData[] boldData;
   private CharacterData[] italicsData;
   private int[] colorCodes;
   private static final int MARGIN = 4;
   private static final String COLOR_INVOKER = "\u0026\u0023\u0031\u0036\u0037\u003b";
   private static int RANDOM_OFFSET = 1;

   public TTFFontRenderer(ExecutorService executorService, ConcurrentLinkedQueue textureQueue, Font font) {
      this(executorService, textureQueue, font, 256);
   }

   public TTFFontRenderer(ExecutorService executorService, ConcurrentLinkedQueue textureQueue, Font font, int characterCount) {
      this(executorService, textureQueue, font, characterCount, true);
   }

   public TTFFontRenderer(ExecutorService executorService, ConcurrentLinkedQueue textureQueue, Font font, boolean antiAlias) {
      this(executorService, textureQueue, font, 256, antiAlias);
   }

   public TTFFontRenderer(ExecutorService executorService, ConcurrentLinkedQueue textureQueue, Font font, int characterCount, boolean antiAlias) {
      this.fractionalMetrics = false;
      this.colorCodes = new int[32];
      this.font = font;
      this.fractionalMetrics = true;
      this.antiAlias = antiAlias;
      int[] regularTexturesIds = new int[characterCount];
      int[] boldTexturesIds = new int[characterCount];
      int[] italicTexturesIds = new int[characterCount];

      for(int i = 0; i < characterCount; ++i) {
         regularTexturesIds[i] = GL11.glGenTextures();
         boldTexturesIds[i] = GL11.glGenTextures();
         italicTexturesIds[i] = GL11.glGenTextures();
      }

      executorService.execute(() -> {
         this.regularData = this.setup(new CharacterData[characterCount], regularTexturesIds, textureQueue, 0);
      });
      executorService.execute(() -> {
         this.boldData = this.setup(new CharacterData[characterCount], boldTexturesIds, textureQueue, 1);
      });
      executorService.execute(() -> {
         this.italicsData = this.setup(new CharacterData[characterCount], italicTexturesIds, textureQueue, 2);
      });
   }

   public void drawCenterOutlinedString(String text, float x, float y, int borderColor, int color) {
      this.drawString(text, x - (float) (this.getStringWidth(text) / 2) - 0.5f, y, borderColor);
      this.drawString(text, x - (float) (this.getStringWidth(text) / 2) + 0.5f, y, borderColor);
      this.drawString(text, x - (float) (this.getStringWidth(text) / 2), y - 0.5f, borderColor);
      this.drawString(text, x - (float) (this.getStringWidth(text) / 2), y + 0.5f, borderColor);
      this.drawString(text, x - (float) (this.getStringWidth(text) / 2), y, color);
   }

   private CharacterData[] setup(CharacterData[] characterData, int[] texturesIds, ConcurrentLinkedQueue textureQueue, int type) {
      this.generateColors();
      Font font = this.font.deriveFont(type);
      BufferedImage utilityImage = new BufferedImage(1, 1, 2);
      Graphics2D utilityGraphics = (Graphics2D)utilityImage.getGraphics();
      utilityGraphics.setFont(font);
      FontMetrics fontMetrics = utilityGraphics.getFontMetrics();

      for(int index = 0; index < characterData.length; ++index) {
         char character = (char)index;
         Rectangle2D characterBounds = fontMetrics.getStringBounds(String.valueOf(character), utilityGraphics);
         float width = (float)characterBounds.getWidth() + 8.0F;
         float height = (float)characterBounds.getHeight();
         BufferedImage characterImage = new BufferedImage(MathHelper.ceil((double)width), MathHelper.ceil((double)height), 2);
         Graphics2D graphics = (Graphics2D)characterImage.getGraphics();
         graphics.setFont(font);
         graphics.setColor(new Color(255, 255, 255, 0));
         graphics.fillRect(0, 0, characterImage.getWidth(), characterImage.getHeight());
         graphics.setColor(Color.WHITE);
         if(this.antiAlias) {
            graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, this.fractionalMetrics?RenderingHints.VALUE_FRACTIONALMETRICS_ON:RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
         }

         graphics.drawString(String.valueOf(character), 4, fontMetrics.getAscent());
         int textureId = texturesIds[index];
         this.createTexture(textureId, characterImage, textureQueue);
         characterData[index] = new CharacterData(this, character, (float)characterImage.getWidth(), (float)characterImage.getHeight(), textureId);
      }

      return characterData;
   }

   private void createTexture(int textureId, BufferedImage image, ConcurrentLinkedQueue textureQueue) {
      int[] pixels = new int[image.getWidth() * image.getHeight()];
      image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth());
      ByteBuffer buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * 4);

      for(int y = 0; y < image.getHeight(); ++y) {
         for(int x = 0; x < image.getWidth(); ++x) {
            int pixel = pixels[y * image.getWidth() + x];
            buffer.put((byte)(pixel >> 16 & 255));
            buffer.put((byte)(pixel >> 8 & 255));
            buffer.put((byte)(pixel & 255));
            buffer.put((byte)(pixel >> 24 & 255));
         }
      }

      buffer.flip();
      textureQueue.add(new TextureData(textureId, image.getWidth(), image.getHeight(), buffer));
   }

   public int drawString(String text, float x, float y, int color) {

      return this.renderString(text, x, y, color, false);
   }

   public static boolean isContainChinese(String str){

      Pattern p = Pattern.compile("[\u4E00-\u9FA5|\\！|\\，|\\。|\\（|\\）|\\《|\\》|\\“|\\”|\\？|\\：|\\；|\\【|\\】]");
      Matcher m = p.matcher(str);
      if (m.find()) {
         return true;
      }
      return false;
   }
   public void drawCenteredString(String text, float x, float y, int color) {
      float width = this.getWidth(text) / 2.0F;
      this.renderString(text, x - width, y, color, false);
   }

   public void drawStringWithShadow(String text, float x, float y, int color) {
      GL11.glTranslated(0.5D, 0.5D, 0.0D);
      this.renderString(text, x, y, color, true);
      GL11.glTranslated(-0.5D, -0.5D, 0.0D);
      this.renderString(text, x, y, color, false);
   }

   private int renderString(String text, float x, float y, int color, boolean shadow) {

      if(text != "" && text.length() != 0) {
         GL11.glPushMatrix();
         GlStateManager.scale(0.5D, 0.5D, 1.0D);
         GlStateManager.enableBlend();
         GlStateManager.blendFunc(770, 771);
         x -= 2.0F;
         y -= 2.0F;
         x += 0.5F;
         y += 0.5F;
         x *= 2.0F;
         y *= 2.0F;
         CharacterData[] characterData = this.regularData;
         boolean underlined = false;
         boolean strikethrough = false;
         boolean obfuscated = false;
         int length = text.length();
         double multiplier = 255.0D * (double)(shadow?4:1);
         Color c = new Color(color);
         GL11.glColor4d((double)c.getRed() / multiplier, (double)c.getGreen() / multiplier, (double)c.getBlue() / multiplier, (double)(color >> 24 & 255) / 255.0D);

         for(int i = 0; i < length; ++i) {
            char character = text.charAt(i);
            char previous = i > 0?text.charAt(i - 1):46;
            if(previous != 167) {
               if(character == 167 && i < length) {
                  int var19 = "0123456789abcdefklmnor".indexOf(text.toLowerCase(Locale.ENGLISH).charAt(i + 1));
                  if(var19 >= 16) {
                     if(var19 == 16) {
                        obfuscated = true;
                     } else if(var19 == 17) {
                        characterData = this.boldData;
                     } else if(var19 == 18) {
                        strikethrough = true;
                     } else if(var19 == 19) {
                        underlined = true;
                     } else if(var19 == 20) {
                        characterData = this.italicsData;
                     } else if(var19 == 21) {
                        obfuscated = false;
                        strikethrough = false;
                        underlined = false;
                        characterData = this.regularData;
                        GL11.glColor4d(1.0D * (shadow?0.25D:1.0D), 1.0D * (shadow?0.25D:1.0D), 1.0D * (shadow?0.25D:1.0D), (double)(color >> 24 & 255) / 255.0D);
                     }
                  } else {
                     obfuscated = false;
                     strikethrough = false;
                     underlined = false;
                     characterData = this.regularData;
                     if(var19 < 0 || var19 > 15) {
                        var19 = 15;
                     }

                     if(shadow) {
                        var19 += 16;
                     }

                     int textColor = this.colorCodes[var19];
                     GL11.glColor4d((double)(textColor >> 16) / 255.0D, (double)(textColor >> 8 & 255) / 255.0D, (double)(textColor & 255) / 255.0D, (double)(color >> 24 & 255) / 255.0D);
                  }
               } else if(character <= 255) {
                  if(obfuscated) {
                     character = (char)(character + RANDOM_OFFSET);
                  }

                  this.drawChar(character, characterData, x, y);
                  CharacterData charData = characterData[character];
                  if(strikethrough) {
                     this.drawLine(new Vector2f(0.0F, charData.height / 2.0F), new Vector2f(charData.width, charData.height / 2.0F), 3.0F);
                  }

                  if(underlined) {
                     this.drawLine(new Vector2f(0.0F, charData.height - 15.0F), new Vector2f(charData.width, charData.height - 15.0F), 3.0F);
                  }

                  x += charData.width - 8.0F;
               }
            }
         }

         GL11.glPopMatrix();
         GlStateManager.disableBlend();
         GlStateManager.bindTexture(0);
         GL11.glColor4d(1.0D, 1.0D, 1.0D, 1.0D);
         return (int)x;
      } else {
         return 0;
      }
   }

   public float getWidth(String text) {
      float width = 0.0F;
      CharacterData[] characterData = this.regularData;
      int length = text.length();

      for(int i = 0; i < length; ++i) {
         char character = text.charAt(i);
         char previous = i > 0?text.charAt(i - 1):46;
         if(previous != 167) {
            if(character == 167 && i < length) {
               int var9 = "0123456789abcdefklmnor".indexOf(text.toLowerCase(Locale.ENGLISH).charAt(i + 1));
               if(var9 == 17) {
                  characterData = this.boldData;
               } else if(var9 == 20) {
                  characterData = this.italicsData;
               } else if(var9 == 21) {
                  characterData = this.regularData;
               }
            } else if(character <= 255) {
               CharacterData charData = characterData[character];
               width += (charData.width - 8.0F) / 2.0F;
            }
         }
      }

      return width + 2.0F;
   }

   public int getStringWidth(String text) {
       if (text == null) {
           return 0;
       }
       int width = 0;
       CharacterData[] characterData = this.regularData;
       boolean bold = false;
       boolean italic = false;
       int size = text.length();
       int i = 0;
       while (i < size) {
           char character = text.charAt(i);
           if (character == '\u00a7' && i < size) {
               int colorIndex = "0123456789abcdefklmnor".indexOf(character);
               if (colorIndex < 16) {
                   bold = false;
                   italic = false;
               } else if (colorIndex == 17) {
                   bold = true;
                   characterData = this.boldData;
               } else if (colorIndex == 20) {
                   italic = true;
                   characterData = this.italicsData;
               } else if (colorIndex == 21) {
                   bold = false;
                   italic = false;
                   characterData = this.regularData;
               }
               ++i;
           } else if (character < characterData.length && character >= '\u0000') {
        	   CharacterData charData = characterData[character];
               width += (charData.width - 8.0F) / 2.0F;
           }
           ++i;
       }
       return width / 2;
   }

   public float getHeight(String text) {
      float height = 0.0F;
      CharacterData[] characterData = this.regularData;
      int length = text.length();

      for(int i = 0; i < length; ++i) {
         char character = text.charAt(i);
         char previous = i > 0?text.charAt(i - 1):46;
         if(previous != 167) {
            if(character == 167 && i < length) {
               int var9 = "0123456789abcdefklmnor".indexOf(text.toLowerCase(Locale.ENGLISH).charAt(i + 1));
               if(var9 == 17) {
                  characterData = this.boldData;
               } else if(var9 == 20) {
                  characterData = this.italicsData;
               } else if(var9 == 21) {
                  characterData = this.regularData;
               }
            } else if(character <= 255) {
               CharacterData charData = characterData[character];
               height = Math.max(height, charData.height);
            }
         }
      }

      return height / 2.0F - 2.0F;
   }

   private void drawChar(char character, CharacterData[] characterData, float x, float y) {
      CharacterData charData = characterData[character];
      charData.bind();
      GL11.glBegin(7);
      GL11.glTexCoord2f(0.0F, 0.0F);
      GL11.glVertex2d((double)x, (double)y);
      GL11.glTexCoord2f(0.0F, 1.0F);
      GL11.glVertex2d((double)x, (double)(y + charData.height));
      GL11.glTexCoord2f(1.0F, 1.0F);
      GL11.glVertex2d((double)(x + charData.width), (double)(y + charData.height));
      GL11.glTexCoord2f(1.0F, 0.0F);
      GL11.glVertex2d((double)(x + charData.width), (double)y);
      GL11.glEnd();
   }

   private void drawLine(Vector2f start, Vector2f end, float width) {
      GL11.glDisable(3553);
      GL11.glLineWidth(width);
      GL11.glBegin(1);
      GL11.glVertex2f(start.x, start.y);
      GL11.glVertex2f(end.x, end.y);
      GL11.glEnd();
      GL11.glEnable(3553);
   }

   private void generateColors() {
      for(int i = 0; i < 32; ++i) {
         int thingy = (i >> 3 & 1) * 85;
         int red = (i >> 2 & 1) * 170 + thingy;
         int green = (i >> 1 & 1) * 170 + thingy;
         int blue = (i >> 0 & 1) * 170 + thingy;
         if(i == 6) {
            red += 85;
         }

         if(i >= 16) {
            red /= 4;
            green /= 4;
            blue /= 4;
         }

         this.colorCodes[i] = (red & 255) << 16 | (green & 255) << 8 | blue & 255;
      }

   }
   class CharacterData
   {
       /**
        * The character the data belongs to.
        */
       public char character;

       /**
        * The width of the character.
        */
       public float width;

       /**
        * The height of the character.
        */
       public float height;

       /**
        * The id of the character texture.
        */
       private int textureId;
       final TTFFontRenderer this0;


       public CharacterData(TTFFontRenderer var1, char character, float width, float height, int textureId) {
    	      this.this0 = var1;
    	      this.character = character;
    	      this.width = width;
    	      this.height = height;
    	      this.textureId = textureId;
    	   }

       /**
        * Binds the texture.
        */
       public void bind()
       {
           // Binds the opengl texture by the texture id.
    	   GL11.glBindTexture(3553, this.textureId);

       }
   }
}
